package io.github.qifan777.server.task.service;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.util.IdUtil;
import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result;
import com.github.binarywang.wxpay.service.WxPayService;
import io.github.qifan777.server.Immutables;
import io.github.qifan777.server.dict.model.DictConstants;
import io.github.qifan777.server.infrastructure.wechat.WxPayPropertiesExtension;
import io.github.qifan777.server.payment.entity.Payment;
import io.github.qifan777.server.payment.entity.PaymentDraft;
import io.github.qifan777.server.payment.entity.PaymentTable;
import io.github.qifan777.server.payment.entity.dto.PaymentPriceView;
import io.github.qifan777.server.payment.model.WeChatPayModel;
import io.github.qifan777.server.payment.service.WeChatPayService;
import io.github.qifan777.server.refund.entity.RefundRecord;
import io.github.qifan777.server.refund.entity.RefundRecordDraft;
import io.github.qifan777.server.refund.repository.RefundRecordRepository;
import io.github.qifan777.server.task.entity.TaskOrder;
import io.github.qifan777.server.task.entity.TaskOrderDraft;
import io.github.qifan777.server.task.entity.TaskOrderFetcher;
import io.github.qifan777.server.task.entity.TaskOrderTable;
import io.github.qifan777.server.task.entity.dto.TaskOrderInput;
import io.github.qifan777.server.task.repository.TaskOrderRepository;
import io.github.qifan777.server.wallet.order.service.WalletOrderService;
import io.github.qifan777.server.wallet.root.service.WalletService;
import io.qifan.infrastructure.common.constants.ResultCode;
import io.qifan.infrastructure.common.exception.BusinessException;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.babyfish.jimmer.sql.JSqlClient;
import org.babyfish.jimmer.sql.ast.Expression;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.Optional;

@Service
@Slf4j
@AllArgsConstructor
@Transactional
public class TaskOrderService {
    private final TaskOrderRepository taskOrderRepository;
    private final JSqlClient jSqlClient;
    private final WeChatPayService weChatPayService;
    private final WxPayService wxPayService;
    private final WxPayPropertiesExtension wxPayPropertiesExtension;
    private final WalletOrderService walletOrderService;
    private final WalletService walletService;

    public String create(TaskOrderInput taskOrderInput) {
        String orderId = IdUtil.fastSimpleUUID();
        CalculateView calculated = calculate(taskOrderInput);
        Payment payment = PaymentDraft.$.produce(
                calculated.getPaymentPriceView().toEntity(),
                draft -> draft
                        .setId(orderId)
                        .setPayType(taskOrderInput.getPayType())
        );
        TaskOrder entity = TaskOrderDraft.$.produce(taskOrderInput
                        .toEntity(),
                draft -> {
                    // 设置订单的id和状态
                    draft.setId(orderId)
                            .setStatus(DictConstants.TaskOrderStatus.TO_BE_PAID);
                    // 设置支付详情
                    draft.baseOrder()
                            .setId(orderId)
                            .setType(DictConstants.OrderType.DELIVER_ORDER)
                            .setPayment(payment);
                });
        var save = taskOrderRepository.save(entity);
        return save.id();
    }

    public synchronized String deliver(String id) {
        TaskOrder taskOrder = taskOrderRepository.findById(id)
                .orElseThrow(() -> new BusinessException(ResultCode.NotFindError, "订单不存在"));
        checkStatus(taskOrder, DictConstants.TaskOrderStatus.TO_BE_ACCEPT);
        TaskOrderTable t = TaskOrderTable.$;
        taskOrderRepository.sql().createUpdate(t)
                .where(t.id().eq(id))
                .set(t.status(), DictConstants.TaskOrderStatus.TO_BE_DELIVERED)
                .set(t.deliverId(), StpUtil.getLoginIdAsString())
                .execute();
        return id;
    }

    public String confirm(String id) {
        TaskOrder taskOrder = taskOrderRepository.findById(id,TaskOrderRepository.COMPLEX_FETCHER_FOR_FRONT)
                .orElseThrow(() -> new BusinessException(ResultCode.NotFindError, "订单不存在"));
        checkStatus(taskOrder, DictConstants.TaskOrderStatus.TO_BE_DELIVERED);
        TaskOrderTable t = TaskOrderTable.$;
        jSqlClient.createUpdate(t)
                .where(t.id().eq(id))
                .set(t.status(), DictConstants.TaskOrderStatus.FINISHED)
                .execute();
        walletService.change(Immutables.createWalletRecord(draft -> {
            draft.setType(DictConstants.WalletRecordType.ACCEPT_ORDER)
                    .setWalletId(StpUtil.getLoginIdAsString())
                    .setAmount(taskOrder.baseOrder().payment().payAmount())
                    .setDescription("接单奖励");
        }));
        return id;
    }


    public String acceptedCancelForDeliver(String id) {
        TaskOrder taskOrder = taskOrderRepository.findById(id)
                .orElseThrow(() -> new BusinessException(ResultCode.NotFindError, "订单不存在"));
        checkStatus(taskOrder, DictConstants.TaskOrderStatus.TO_BE_DELIVERED);
        if (!taskOrder.deliver().id().equals(StpUtil.getLoginIdAsString())) {
            throw new BusinessException(ResultCode.NotFindError, "非本人操作");
        }
        TaskOrderTable t = TaskOrderTable.$;
        taskOrderRepository.sql().createUpdate(t)
                .where(t.id().eq(id))
                .set(t.status(), DictConstants.TaskOrderStatus.TO_BE_ACCEPT)
                .set(t.deliverId(), Expression.nullValue(String.class))
                .execute();
        return id;
    }


    public WxPayUnifiedOrderV3Result.JsapiResult prepay(String id) {
        TaskOrder taskOrder = taskOrderRepository.findById(id, TaskOrderRepository.COMPLEX_FETCHER_FOR_FRONT)
                .orElseThrow(() -> new BusinessException(ResultCode.NotFindError));
        checkStatus(taskOrder, DictConstants.TaskOrderStatus.TO_BE_PAID);
        checkOwner(taskOrder);
        WxPayUnifiedOrderV3Result.JsapiResult prepay = weChatPayService.prepay(new WeChatPayModel().setBaseOrder(taskOrder.baseOrder())
                .setExpiredMinutes(5)
                .setNotifyUrl("/front/task-order/notify/pay/wechat"));
        log.info("预支付订单内容：{}", prepay);
        return prepay;
    }

    @SneakyThrows
    public String paymentNotifyWechat(String body, SignatureHeader signatureHeader) {
        WxPayNotifyV3Result.DecryptNotifyResult notifyResult = wxPayService.parseOrderNotifyV3Result(body, signatureHeader)
                .getResult();
        log.info("支付回调:{}", notifyResult);
        String outTradeNo = notifyResult.getOutTradeNo();
        TaskOrder taskOrder = taskOrderRepository.findById(outTradeNo,
                TaskOrderRepository.COMPLEX_FETCHER_FOR_FRONT).orElseThrow(
                () -> new BusinessException(ResultCode.NotFindError, "订单不存在"));
        // 设置微信支付订单id
        StpUtil.switchTo(taskOrder.creator().id());
        PaymentTable t1 = PaymentTable.$;
        jSqlClient.createUpdate(t1)
                .where(t1.baseOrder().id().eq(taskOrder.id()))
                .set(t1.tradeNo(), notifyResult.getTransactionId())
                .set(t1.payTime(), LocalDateTime.now())
                .execute();
        changeStatus(taskOrder.id(), DictConstants.TaskOrderStatus.TO_BE_ACCEPT);
        return taskOrder.id();
    }


    public String unpaidCancelForUser(String id) {
        TaskOrder taskOrder = taskOrderRepository.findById(id, TaskOrderRepository.COMPLEX_FETCHER_FOR_FRONT)
                .orElseThrow(() -> new BusinessException(ResultCode.NotFindError, "订单不存在"));
        checkStatus(taskOrder, DictConstants.TaskOrderStatus.TO_BE_PAID);
        checkOwner(taskOrder);
        changeStatus(taskOrder.id(), DictConstants.TaskOrderStatus.CLOSED);
        return taskOrder.id();
    }

    @SneakyThrows
    public String paidCancelForAdmin(String orderId) {
        TaskOrder taskOrder = taskOrderRepository.findById(orderId, TaskOrderRepository.COMPLEX_FETCHER_FOR_FRONT)
                .orElseThrow(() -> new BusinessException(ResultCode.NotFindError, "订单不存在"));
        checkStatus(taskOrder, DictConstants.TaskOrderStatus.TO_BE_ACCEPT);
        String refundOrderId = IdUtil.fastSimpleUUID();
        RefundRecord refundRecord = RefundRecordDraft.$.produce(draft -> {
            draft.setId(refundOrderId);
            WxPayRefundV3Request wxPayRefundV3Request = new WxPayRefundV3Request().setOutTradeNo(
                            orderId)
                    .setOutRefundNo(refundOrderId)
                    .setNotifyUrl(wxPayPropertiesExtension.getNotifyUrl() + "/front/handicraft-order/notify/refund/wechat")
                    .setReason("退款")
                    .setAmount(new WxPayRefundV3Request
                            .Amount()
                            // 计算价格
                            .setRefund(taskOrder.baseOrder().payment().payAmount()
                                    .multiply(BigDecimal.valueOf(100)).intValue())
                            .setTotal(taskOrder.baseOrder().payment().payAmount()
                                    .multiply(BigDecimal.valueOf(100)).intValue())
                            .setCurrency("CNY"));
            WxPayRefundV3Result wxPayRefundV3Result = wxPayService.refundV3(wxPayRefundV3Request);
            draft.setRefundId(wxPayRefundV3Result.getRefundId())
                    .setStatus(DictConstants.RefundStatus.REFUNDING)
                    .setOrderId(orderId)
                    .setAmount(taskOrder.baseOrder().payment().payAmount())
                    .setRefundApplicationDetails(wxPayRefundV3Result)
                    .setReason("退款");
        });
        return jSqlClient.save(refundRecord).getModifiedEntity().id();
    }

    @SneakyThrows
    public String refundNotifyWeChat(String body, SignatureHeader signatureHeader) {
        WxPayRefundNotifyV3Result.DecryptNotifyResult result = wxPayService.parseRefundNotifyV3Result(body, signatureHeader)
                .getResult();
        log.info("退款回调：{}", result);
        RefundRecord refundRecord = Optional.ofNullable(jSqlClient.findById(RefundRecordRepository.COMPLEX_FETCHER_FOR_FRONT, result.getOutRefundNo()))
                .orElseThrow(() -> new BusinessException(ResultCode.NotFindError, "退款订单不存在"));
        TaskOrder taskOrder = taskOrderRepository.findById(refundRecord.orderId(), TaskOrderFetcher.$.creator())
                .orElseThrow(() -> new BusinessException(ResultCode.NotFindError, "订单不存在"));
        StpUtil.switchTo(taskOrder.creator().id());
        if (result.getRefundStatus().equals("SUCCESS")) {
            // 需要将订单回退到未支付状态再取消
            log.info("将订单状态回退到待支付");
            changeStatus(result.getOutTradeNo(), DictConstants.TaskOrderStatus.TO_BE_PAID);
            log.info("取消订单订单");
            unpaidCancelForUser(result.getOutTradeNo());
            log.info("将订单状态设置为已退款");
            changeStatus(result.getOutTradeNo(), DictConstants.TaskOrderStatus.REFUNDED);
            log.info("生成退款记录");
            jSqlClient.save(RefundRecordDraft.$.produce(refundRecord, draft -> {
                draft.setRefundNotifyDetails(result)
                        .setStatus(DictConstants.RefundStatus.SUCCESS)
                ;
            }));
        } else {
            jSqlClient.save(RefundRecordDraft.$.produce(refundRecord, draft -> draft.setRefundNotifyDetails(result)
                    .setStatus(DictConstants.RefundStatus.FAILED)));
        }

        return refundRecord.id();
    }

    public String walletPay(String id) {
        TaskOrder taskOrder = taskOrderRepository.findById(id, TaskOrderRepository.COMPLEX_FETCHER_FOR_FRONT)
                .orElseThrow(() -> new BusinessException(ResultCode.NotFindError));
        checkStatus(taskOrder, DictConstants.TaskOrderStatus.TO_BE_PAID);
        checkOwner(taskOrder);
        walletService.change(Immutables.createWalletRecord(draft -> {
            draft.setType(DictConstants.WalletRecordType.BUY)
                    .setWalletId(StpUtil.getLoginIdAsString())
                    .setAmount(taskOrder.baseOrder().payment().payAmount().negate())
                    .setDescription("发布订单支付");
        }));
        changeStatus(taskOrder.id(), DictConstants.TaskOrderStatus.TO_BE_ACCEPT);
        return id;
    }

    @Data
    @Accessors(chain = true)
    public static class CalculateView {
        PaymentPriceView paymentPriceView;
    }

    public CalculateView calculate(TaskOrderInput taskOrderInput) {
        Payment payment = Immutables.createPayment(draft -> {
            draft.setDeliveryFee(BigDecimal.ZERO)
                    .setCouponAmount(BigDecimal.ZERO)
                    .setVipAmount(BigDecimal.ZERO);
            // 计算原始总价
            BigDecimal totalPrice = taskOrderInput.getWeight().multiply(BigDecimal.valueOf(2));
            draft.setProductAmount(totalPrice);
            // 计算vip优惠
            // 计算支付金额
            draft.setPayAmount(
                    draft.productAmount()
                            .add(draft.deliveryFee())
                            .subtract(draft.couponAmount())
                            .subtract(draft.vipAmount())
            );
        });
        return new CalculateView().setPaymentPriceView(new PaymentPriceView(payment));
    }

    public void changeStatus(String id, DictConstants.TaskOrderStatus orderStatus) {
        var t = TaskOrderTable.$;
        taskOrderRepository.sql().createUpdate(t)
                .where(t.id().eq(id))
                .set(t.status(), orderStatus)
                .execute();
    }

    public void checkStatus(TaskOrder taskOrder, DictConstants.TaskOrderStatus... orderStatuses) {
        for (var status : orderStatuses) {
            if (taskOrder.status().equals(status)) {
                return;
            }
        }
        throw new BusinessException(ResultCode.ParamSetIllegal, "订单状态不正确");
    }

    public void checkOwner(TaskOrder taskOrder) {
        if (!taskOrder.creator().id().equals(StpUtil.getLoginIdAsString())) {
            if (StpUtil.hasRole("管理员")) {
                return;
            }
            throw new BusinessException(ResultCode.ParamSetIllegal, "非本人操作");
        }
    }
}